package exec

import (
	"context"
	"fmt"
	"github.com/Unixeno/islands/cmd/connection"
	"github.com/Unixeno/islands/cmd/container"
	"github.com/Unixeno/islands/cmd/io"
	"github.com/Unixeno/islands/cmd/streams"
	"github.com/Unixeno/islands/cmd/utils"
	"github.com/Unixeno/islands/pb"
	"github.com/spf13/cobra"
	"os"
	"sync"
)

var (
	interactive bool
	tty         bool
	privileged  bool
	workdir     string
	user        string
	env         []string
)

func GetExecCmd() *cobra.Command {
	execCmd := &cobra.Command{
		Use:   "exec container [command]",
		Short: "Run a command in a running container",
		Long:  `Run a command in a running container`,
		Args:  cobra.MinimumNArgs(1),
		Run:   execHandle,
	}

	execCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
	execCmd.Flags().BoolVarP(&tty, "tty", "t", false, "Allocate a pseudo-TTY")
	execCmd.Flags().BoolVar(&privileged, "privileged", false, "Give extended privileges to the command")
	execCmd.Flags().StringVarP(&workdir, "workdir", "w", "/", "Working directory inside the container")
	execCmd.Flags().StringVarP(&user, "user", "u", "root", "Username or UID (format: <name|uid>[:<group|gid>])")
	execCmd.Flags().StringSliceVarP(&env, "env", "e", []string{}, "Set environment variables")

	return execCmd
}

func execHandle(cmd *cobra.Command, args []string) {
	id := container.CheckNameOrId(args[0])

	// createExec
	if r, err := connection.Client.CreateExecInstance(context.Background(), &pb.CreateExecInstanceReq{
		Id:         id,
		Cmd:        args[1:],
		Tty:        tty,
		Env:        env,
		WorkingDir: workdir,
	}); err != nil {
		fmt.Printf("Cannot exec the container for reason %v\n", err)
	} else {
		execId := r.Id

		// TODO: 分离
		group := &sync.WaitGroup{}
		stream, err := connection.Client.AttachExec(context.Background())

		if err != nil {
			panic("")
		}

		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()

		if err := stream.Send(&pb.AttachStreamIn{
			Id:      execId,
			Content: nil,
		}); err != nil {
			fmt.Println(err)
		}

		opts := io.AttachOptions{
			Tty:          tty,
			AttachStdin:  true,
			AttachStdout: true,
			Container:    false,
			Id:           execId,
		}

		islandsIo := io.IslandsIO{
			In:   streams.NewIn(os.NewFile(uintptr(0), "/dev/tty")),
			Out:  streams.NewOut(os.Stdout),
			Opts: opts,
			GrpcIO:  *io.NewGrpcIO(execId, stream),
		}

		group.Add(1)
		go func(waitGroup *sync.WaitGroup) {
			islandsIo.Stream(ctx, group)
		}(group)

		if e, err := connection.Client.StartExecInstance(context.Background(), &pb.ExecID{
			Id: execId,
		}); err != nil {
			fmt.Println(err)
			return
		} else {
			if !utils.PrintError(e) {
			}
		}

		group.Wait()
	}
}
